## comments in this file which start with '##' are instructions for turning the stimulus_template
##     into a new stimulus.
import scipy
import copy
## import general_stimulus, which takes care of the main drawing loop and listening for
##     a pulse on the parallel port.
from helper_codes.general_stimulus import general_stimulus, GeneralStim

## everywhere you see 'stimulus_template' you should replace it with whatever you're calling
##     your new stimulus.
## One way to do this is in vi is to execute the following line:
##     :%s/stimulus_template/ && /g
##       where && is the name of your stimulus.

## Run_experiment expects a function which has the same name as your stimulus and ends with 
##     _settings    The docstring should be enough to tell you what you need to change.
def stimulus_template_settings():
    '''
    This function tells you what settings are needed and in what order for this stimulus.
    Inputs:
        None
    Returns:
        settings        : A list of tuples.  In each tuple is
                            (setting_name, setting_description, default_value).
                          The list is of length num_settings + 1, so that the final
                             value in the list is True/False wether or not the stimulus
                             can be triggered via the parallel port or not.
    '''
    settings = []
    settings.append( ('dot_size',
                     'The size of the moving dot. (normalized RF units)',
                     1.0 ))
    settings.append( ('duration',
                     'The amount of time the stimulus will move. (seconds)',
                     3.5 ))
    settings.append( ('wait_time',
                     'The amount of time after stimulus movement. (seconds)',
                     1.0 ))
    settings.append( ('start_pos',
                     'Where the stimulus will first appear. (normalized RF units) [list]',
                     [-0.5,-0.5] ))
    settings.append( ('end_pos',
                     'Where the stimulus will move towards and end up. (normalized RF units) [list]',
                     [-1.0, 1.0] ))
    settings.append( ('shade',
                     '1.0 for black dots, -1.0 for white dots.',
                     1.0 ))
    settings.append( ('mirror_pos',
                     'The position of a "mirrored" stimulus relative to the RF. (normalized RF units) [list]',
                     [0.0,0.0] ))
    settings.append( ('delay',
                     'Time between showing the mirror and the regular stimulus (seconds) negative values supported.',
                     0.0 ))
    settings.append( ('win_size',
                     'The dimensions of the stimulus delivery window. (pixels) [list]',
                     [1440,900] ))
    settings.append( ('refresh_rate',
                     'The number of screen refreshes per second. (Hz)',
                     60 ))
## If this stimulus cannot be triggered, make sure this is False
    settings.append(True) # this stimulus can be triggered
    return settings

## in addition, every stimulus must have a function which tells Run_experiment how long it will run
##     when triggered.  Change the name to the name of your stimulus, and update the parameters to 
##     the same as those you specified above.  Use those parameters to calculate how long 
##     the stimulus will run, this will be different for different stimuli.
def stimulus_template_runtime(dot_size, duration, wait_time, start_pos, end_pos, shade, 
               mirror_pos, delay, win_size, refresh_rate):
    """
    Calculate and return the number of seconds that this stimulus will need to run, after being triggered.
    """
    return duration + wait_time + abs(delay) + 0.99 # 0.99 added so rounding is always up.

## This class will determine the behavior of your stimulus.
class Stim(GeneralStim):
    """
    This is a basic stimulus object, it must have:
        .setup(window) method to set itself up to be drawn
        .draw() method to draw itself to the display
        .evolve() method to set itself up for the next draw
        .exhausted variable set to True after stim is done.
    """
## The parameters here should be all the ones in stimulus_template_runtime (with 
##      the exception of 'win_size') plus 'preview' and 'save_frames'.
    def __init__( self, dot_size, duration, wait_time, start_pos, end_pos, shade, 
                  mirror_pos, delay, refresh_rate, preview, save_frames):
        self.dot_size = dot_size
        self.duration = duration
        self.wait_time = wait_time
        ## I'm turning start_pos and end_pos into arrays so I can easily add other
        ##     positions to them component-wise 
        self.start_pos = scipy.array(start_pos)
        self.end_pos = scipy.array(end_pos)
        self.shade = shade
        self.mirror_pos = scipy.array(mirror_pos)
        self.delay = delay
        self.refresh_rate = refresh_rate
        self.preview = preview
        self.save_frames = save_frames

## self.exhausted variable must exist and should be False unless the stim is all done.
##     when this variable is set to True, the program knows to stop the stimulus.
        self.exhausted = False
        self.times_evolved = 0

## In the setup function, you should create all the things you'll later need to draw the
##    stim.  The idea is that you want the drawing to be very fast, and you don't want to
##    do anything complicated in the draw function.  Likewise for the evolve function.
## So in this function we set up the textures or PatchStim objects that we'll draw
##    later.  We also pre-calculate all the positions for every frame.  This could be done
##    in the evolve function, but only consider that if you CAN'T do it here.
    def setup(self, window):
        from psychopy import visual

        # how many frames we'll display, from this point on, we'll deal in frames
        #     instead of time in seconds.
        num_frames = int(self.duration*self.refresh_rate)
        wait_frames  = int(self.wait_time*self.refresh_rate)
        delay_frames = int(abs(self.delay)*self.refresh_rate)

        # how the dot should move each frame
        dx         = (self.end_pos[0]-self.start_pos[0])/(num_frames*1.0)
        dy         = (self.end_pos[1]-self.start_pos[1])/(num_frames*1.0)
        positions = [[self.start_pos[0]+dx*i, self.start_pos[1]+dy*i] 
                      for i in xrange(num_frames)]

        # repeat the final position for as long needed based on the wait_time
        last_position = self.end_pos
        for i in xrange(wait_frames):
            positions.append( last_position )

## Here we save the positions and num_frames and delay_frames to be used in the evolve function.
        self.positions = scipy.array(positions)
        self.num_frames = num_frames
        self.delay_frames = delay_frames

        # make the textures of the dots.
## Psychopy needs a matrix to define the luminance of the PatchStim, and it must be
##    n x n where n is a power of 2.  Since we want a solid color, we make the 
##    matrix all one value, but if we wanted a checkerboard or something, we could
##    make the matrix have alternating values.
        texture = scipy.ones((2,2),dtype=scipy.float64)*-1.0*self.shade
        self.dot = visual.PatchStim( window, 
                                        tex=texture, 
                                        pos=self.positions[0],
                                        size=(self.dot_size, self.dot_size), 
                                        mask='circle',
                                        units='norm')
        self.mirror_dot = visual.PatchStim( window, 
                                        tex=texture, 
                                        pos=self.positions[0],
                                        size=(self.dot_size, self.dot_size), 
                                        mask='circle',
                                        units='norm')
## Whatever PatchStims you create, make sure you put them into a list called 
##     self.textures, this will make sure they are disposed of when the 
##     stimulus ends instead of hanging around using up memory.  This should
##     not be necessary in future versions of Psychopy.
        self.textures = [self.dot, self.mirror_dot]


## Keep this very simple.
    def draw(self):
        self.dot.draw()
        self.mirror_dot.draw()

## Try to keep this function FAST, we did this by pre-calculating all the positions in the
##    setup function above.  This function is called every frame to update the textures (or
##    PatchStims or whatever is being drawn) for the next frame.  When the stim is all done
##    this function sets self.exhausted to True.
    def evolve(self):
        # self.times_evolved is the number of frames that have been drawn so far.
        self.times_evolved += 1

        ## we need to determine which position, from the positions list, to assign for the
        ##     dot and the mirror_dot.  First we find the index from the position list.
        if self.delay < 0.0: # mirror_dot goes first
            mirror_dot_pos_i = min(len(self.positions)-1, self.times_evolved)
            dot_pos_i = max(0, self.times_evolved-self.delay_frames)
        else: # dot goes first, then mirror
            mirror_dot_pos_i = max(0, self.times_evolved-self.delay_frames)
            dot_pos_i = min(len(self.positions)-1, self.times_evolved)

        ## Then we set the positions of the PatchStims
        self.dot.setPos(self.positions[dot_pos_i])
        self.mirror_dot.setPos(self.positions[mirror_dot_pos_i]+self.mirror_pos)

        ## Finally, we check to see if we're done with the stimulus, and if so we set
        ##     self.exhausted to True, if not, we reset self.times_evolved so the stimulus
        ##     will keep on going.
        if self.times_evolved >= len(self.positions)-1 + self.delay_frames:
            if (not self.preview) or self.save_frames:
                self.exhausted = True
            else:
                self.times_evolved = 0 # continue looping forever in preview mode.

## This is the actual stimulus function that you will call to run the stimulus.  It should
##    take all the same args as stimulus_template_runtime, with the addition of all the
##    keyword args below.
def stimulus_template(dot_size, duration, wait_time, start_pos, end_pos, shade, 
               mirror_pos, delay, win_size, refresh_rate,
                     window=None,
                     eye_cup=None, 
                     rf=None, 
                     save_frames=False,
                     save_frames_factor=1.0,
                     save_frames_size=100,
                     display_saved_frames=False,
                     preview=True):
## make sure you update the short description of the stimulus here, you shouldn't have to change
##    the Inputs or Returns sections.
    """
    This stimulus shows a single black dot on a white background
    Inputs:
        **kwargs**
        window               : If not None, then a psychopy Window object to draw to.
        eye_cup              : an Eye_cup object, see its docstring for more info.
        rf                   : RF object, to set up new coordinate system.
        save_frames          : If True, save the images for each frame.
        save_frames_factor   :  How large an area should we save?  If 1.0 only save the frame 
                               for an area inside the RF box, 2.0 would be a box twice the size
                               of the RF.
        save_frames_size     : What resolution should the saved frame be?
        display_saved_frames : If False, frames are constructed but not drawn to the screen.  This
                               can be much faster if all you want are the saved frames.
        preview              : If False the program will wait for a pulse trigger, otherwise it won't.
    Returns:
        return_values   : if save_frames:
                              frame_times, frames
                          else:
                              frame_times 
    """
## here we just create a Stim object (defined above) and put it in a list.  The general_stimulus
##     function expects a list of Stim objects, so if you want you can make a composite stimulus 
##     by just putting numerous Stim objects into a list, they will be drawn in the order listed
##     one on top of the other.  For most stimuli you'll just have one Stim object which does
##     everything.
    stim_list = [ Stim( dot_size, duration, wait_time, start_pos, end_pos, shade, 
                        mirror_pos, delay, refresh_rate, preview, save_frames) ]
## you probably don't have to change the next line.
    return_values = general_stimulus( stim_list, shade, win_size, 
                                         window=window,
                                         eye_cup=eye_cup, 
                                         rf=rf, 
                                         save_frames=save_frames,
                                         save_frames_factor=save_frames_factor,
                                         save_frames_size=save_frames_size,
                                         display_saved_frames=display_saved_frames,
                                         preview=preview)
    return return_values

## Code down here isn't run unless the stimulus is run alone (not in Run_experiment)
##    Use this to test your stimulus without having to use Run_experiment.
if __name__ == '__main__':
   
    # ---------------------------------
    # -- Set the stimulus parameters --
    # ---------------------------------
    dot_size   = 0.1         # diameter of dot in rf units
    duration   = 10.0        # in seconds
    wait_time  = 5.0
    start_pos  = (0.0, 0.0)  # in rf units
    end_pos    = (-0.5,-0.5) # in rf units
    shade      = 1.0         # 1.0 for white, -1.0 for black background
    mirror_pos = [-0.5,0.5]
    delay      = -2.0

    # display parameters
    win_size = (1440,900)  # in pixels
    refresh_rate = 60.0    # in Hz

    return_values = stimulus_template(dot_size, duration, wait_time, start_pos, 
               end_pos, shade, mirror_pos, delay, win_size, refresh_rate,
                save_frames = True,
                display_saved_frames = True)

 
